﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Xml.Linq;
using Microsoft.SharePoint.WebControls;
using System.Reflection;
using System.Xml;
using System.ComponentModel;
using Slam.Internal;

namespace Slam.FieldTypes
{
	public class SlamField : SPFieldMultiLineText
	{
		public string CustomProperties
		{
			get
			{
				string customProperties = "";
				object oldFieldAttribute = this.GetFieldAttributeValueInternal("CustomProperties");
				object fieldAttribute = this.GetFieldAttributeValueInternal("CustomProperties", "aws");
				if (fieldAttribute != null)
				{
					customProperties = fieldAttribute.ToString();
				} 
				else if (oldFieldAttribute != null)
				{
					customProperties = oldFieldAttribute.ToString();
				}
				return customProperties;
			}
			set
			{
				RemoveFieldAttributeValueInternal("CustomProperties");
				SetFieldAttributeValueInternal("aws:CustomProperties", "aws", value.ToString());
			}
		}

		public string Actions
		{
			get
			{
				string actions = "";

				object fieldAttribute = this.GetFieldAttributeValueInternal("Actions", "aws");

				if (!String.IsNullOrEmpty(Convert.ToString(fieldAttribute)))
				{
					actions = fieldAttribute.ToString();
				}
				else
				{
					actions = new XElement("Actions").ToString();
				}
				
				return actions;
			}
			set
			{
				SetFieldAttributeValueInternal("aws:Actions", "aws", value.ToString());
			}

		}

		public virtual string LookupList
		{
			get
			{
				return (string)GetCustomPropertyValue("LookupList");
			}
			set
			{
				SetCustomProperty("LookupList", typeof(string), value);
			}
		}

		public string UserControl
		{
			get
			{
				string userControl = "";
				object oldFieldAttribute = this.GetFieldAttributeValueInternal("UserControl");
				object fieldAttribute = this.GetFieldAttributeValueInternal("UserControl", "aws");
				if (fieldAttribute != null)
				{
					userControl = fieldAttribute.ToString();
				} 
				else if (oldFieldAttribute != null)
				{
					userControl = oldFieldAttribute.ToString();
				}
				return userControl;
			}
			set
			{
				RemoveFieldAttributeValueInternal("UserControl");
				SetFieldAttributeValueInternal("aws:UserControl", "aws", value.ToString());
			}
		}

		public SlamField(SPFieldCollection fields, string fieldName)
			: base(fields, fieldName)
		{	
		}

		public SlamField(Microsoft.SharePoint.SPFieldCollection fields, string typeName, string displayName)
			: base(fields, typeName, displayName)
		{	
		}

		public object GetCustomPropertyValue(string name)
		{
			object propertyValue = null;
			if (String.IsNullOrEmpty(CustomProperties))
			{
				CustomProperties = new XElement("CustomProperties").ToString();
			}
			XDocument customProperties = XDocument.Parse(CustomProperties);
			var matchedProperties = (from e in customProperties.Root.Elements()
									 where e.Attribute("Name").Value == name
									 select e);
			if (matchedProperties.Count() > 0)
			{
				XElement matchedProperty = matchedProperties.First();
				string type = matchedProperty.Attribute("Type").Value;
				string value = matchedProperty.Attribute("Value").Value;
				System.Type matchedType = System.Type.GetType(type);
				if (matchedType != null && matchedType.IsSubclassOf(typeof(Enum)))
				{
					propertyValue = Enum.Parse(System.Type.GetType(type), value, false);
				}
				else if(matchedType != null)
				{
					propertyValue = Convert.ChangeType(value, System.Type.GetType(type));
				}
			}
			return propertyValue;
		}

		public void SetCustomProperty(string name, Type type, object value)
		{
			if (String.IsNullOrEmpty(CustomProperties))
			{
				CustomProperties = new XElement("CustomProperties").ToString();
			}
			XDocument customProperties = XDocument.Parse(CustomProperties);
			var matchedProperties = (from e in customProperties.Root.Elements()
									 where e.Attribute("Name").Value == name
									 select e);
			if (matchedProperties.Count() > 0)
			{
				XElement matchedProperty = matchedProperties.First();
				matchedProperty.SetAttributeValue("Name", name);
				matchedProperty.SetAttributeValue("Type", type.AssemblyQualifiedName);
				matchedProperty.SetAttributeValue("Value", value.ToString());
			}
			else
			{
				customProperties.Root.Add(new XElement("Property",	new XAttribute("Name", name), 
																	new XAttribute("Type", type.AssemblyQualifiedName),
																	new XAttribute("Value", value.ToString())));
			}
			CustomProperties = customProperties.ToString();
		}

		public void SetRichTextMode(bool enableRichText, SPRichTextMode richTextMode)
		{
			SetFieldAttributeValueInternal("RichText", enableRichText.ToString().ToUpper());
			if (enableRichText)
				SetFieldAttributeValueInternal("RichTextMode", richTextMode.ToString());
			else
				RemoveFieldAttributeValueInternal("RichTextMode");
		}

		#region Actions Methods

		public List<SlamFieldActionMethod> GetActions()
		{	
			XDocument actions = XDocument.Parse(Actions);

			List<SlamFieldActionMethod> listActions = new List<SlamFieldActionMethod>();
			if(!String.IsNullOrEmpty(Actions))
			{

				foreach (XElement action in actions.Root.Elements())
				{
					SlamFieldActionMethod actionMethod = new SlamFieldActionMethod();
					actionMethod.ActionType = (SlamFieldActionType)Enum.Parse(typeof(SlamFieldActionType), action.Attribute("ActionType").Value);
					actionMethod.DisplayName = action.Attribute("DisplayName").Value;
					actionMethod.Class = action.Attribute("Class").Value;
					actionMethod.Method = action.Attribute("Method").Value;
					actionMethod.ExecutionOrder = int.Parse(action.Attribute("ExecutionOrder").Value);
					actionMethod.MethodDefinition = actionMethod.ActionType.ToString() + "; " + actionMethod.Class + "; " + actionMethod.Method;
					actionMethod.Synchronous = action.Attribute("Synchronous") == null ? true : bool.Parse(action.Attribute("Synchronous").Value);

					listActions.Add(actionMethod);
				}

				return listActions;
			}
			else
				return null;
		}
		
		public void SetActions(SlamFieldActionMethod actionMethod)
		{	
			XDocument actions = XDocument.Parse(Actions);
			var matchedActions = (from e in actions.Root.Elements()
								  where e.Attribute("Method").Value == actionMethod.Method && e.Attribute("Class").Value == actionMethod.Class
								  select e);

			if (matchedActions.Count() > 0)
			{
				XElement matchedProperty = matchedActions.First();
				matchedProperty.SetAttributeValue("ActionType", actionMethod.ActionType.ToString());
				matchedProperty.SetAttributeValue("DisplayName", actionMethod.DisplayName);
				matchedProperty.SetAttributeValue("Class", actionMethod.Class);
				matchedProperty.SetAttributeValue("Method", actionMethod.Method);
				matchedProperty.SetAttributeValue("ExecutionOrder", actionMethod.ExecutionOrder.ToString());
				matchedProperty.SetAttributeValue("Synchronous", actionMethod.Synchronous.ToString());
			}
			else
			{
				actions.Root.Add(new XElement("Action", new XAttribute("ActionType", actionMethod.ActionType.ToString()),
																	new XAttribute("DisplayName",actionMethod.DisplayName),
																	new XAttribute("Class", actionMethod.Class),
																	new XAttribute("Method", actionMethod.Method),
																	new XAttribute("ExecutionOrder", actionMethod.ExecutionOrder.ToString()),
																	new XAttribute("Synchronous", actionMethod.Synchronous.ToString())));
			}
			Actions = actions.ToString();
		}
		#endregion
		
	   

		public override BaseFieldControl FieldRenderingControl
		{
			get
			{
				SlamFieldControl field = System.Type.GetType(UserControl).CreateInstance<SlamFieldControl>();

				if (!String.IsNullOrEmpty(CustomProperties))
				{
					XDocument customProperties = XDocument.Parse(CustomProperties);

					field.CustomProperties = CustomProperties;

					List<PropertyInfo> baseProperties = new List<PropertyInfo>(field.GetType().BaseType.GetProperties());

					foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(field))
					{
						//if (property.Name == "CustomProperties")
						//{
						//    property.SetValue(field, CustomProperties);
						//}

						if (!baseProperties.Any(p => p.Name == property.Name))
						{
							var node = from propertyNode in customProperties.Root.Elements()
									   where propertyNode.Attribute("Name").Value == property.Name
									   select propertyNode;


							XElement selectedNode = node.SingleOrDefault();
							if (selectedNode != null)
							{
								string type = (string)selectedNode.Attribute("Type");
								if (System.Type.GetType(type).IsSubclassOf(typeof(Enum)))
								{
									//property.SetValue(field, Enum.Parse(System.Type.GetType(type), selectedNode.Value, false));
									property.SetValue(field, Enum.Parse(System.Type.GetType(type), (string)selectedNode.Attribute("Value"), false));
								}
								else
								{
									property.SetValue(field, Convert.ChangeType((string)selectedNode.Attribute("Value"), System.Type.GetType(type)));
								}
							}
						}
					}
				}

				if (!String.IsNullOrEmpty(Actions))
				{
					field.Actions = Actions;
				}

				return field;
			}
		}

		/// <summary>
		/// Gets an attribute value by name and namespace from the Field's underlying XML.
		/// </summary>
		/// <param name="attribute">Attribute name</param>
		/// <param name="namespaceUri">Attribute namespace</param>
		/// <returns>Attribute value</returns>
		internal string GetFieldAttributeValueInternal(string attribute, string namespaceUri)
		{
			Type fieldType = typeof(SlamField);
			PropertyInfo fieldNode = fieldType.GetProperty("Node", BindingFlags.Instance | BindingFlags.NonPublic);

			return GetNamedStringItemInternal(((XmlNode)fieldNode.GetValue(this, null)), attribute, namespaceUri);
		}

		/// <summary>
		/// Gets an attribute value by name from the Field's underlying XML.
		/// </summary>
		/// <param name="attribute">Attribute name</param>
		/// <returns>Attribute value</returns>
		internal string GetFieldAttributeValueInternal(string attribute)
		{
			Type fieldType = typeof(SlamField);
			MethodInfo method = fieldType.GetMethod("GetFieldAttributeValue", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(string) }, null);
			object attributeValue = method.Invoke(this, new object[] { attribute });
			return attributeValue == null ? "" : attributeValue.ToString();
		}

		/// <summary>
		/// Gets string value of attribute with the given name and namespace from the given XmlNode.
		/// </summary>
		/// <param name="node">Node with attribute</param>
		/// <param name="localName">Attribute LocalName</param>
		/// <param name="namespaceUri">Attribute Namespace prefix</param>
		/// <returns>Attribute value</returns>
		internal static string GetNamedStringItemInternal(XmlNode node, string localName, string namespaceUri)
		{
			XmlAttributeCollection attributes = node.Attributes;
			XmlNode namedItem = attributes.GetNamedItem(localName, namespaceUri);
			return namedItem == null ? null : namedItem.Value;
		}

		/// <summary>
		/// Sets attribute with the given name and namespace in the Field's underlying XML to the given value.
		/// </summary>
		/// <param name="attribute">Attribute name</param>
		/// <param name="namespaceUri">Attribute namespace prefix</param>
		/// <param name="value">Attribute value</param>
		internal void SetFieldAttributeValueInternal(string attribute, string namespaceUri, string value)
		{
			Type fieldType = typeof(SlamField);
			PropertyInfo fieldNode = fieldType.GetProperty("Node", BindingFlags.Instance | BindingFlags.NonPublic);
			SetNamedStringItemInternal(((XmlNode)fieldNode.GetValue(this, null)).OwnerDocument, ((XmlNode)fieldNode.GetValue(this, null)), attribute, namespaceUri, value);
		}

		/// <summary>
		/// Sets attribute with the given name in the Field's underlying XML to the given value
		/// </summary>
		/// <param name="attribute"></param>
		/// <param name="value"></param>
		internal void SetFieldAttributeValueInternal(string attribute, string value)
		{
			Type typeFromHandle = typeof(SlamField);
			MethodInfo method = typeFromHandle.GetMethod("SetFieldAttributeValue", BindingFlags.Instance | BindingFlags.NonPublic);
			method.Invoke(this, new object[] { attribute, value });
		}

		/// <summary>
		/// Sets attribute in the given XmlNode in the given XmlDocument with the given name and namespace to the given value.
		/// </summary>
		/// <param name="document">XML document containing the node with the attribute to set</param>
		/// <param name="node">Node containing the attribute to set</param>
		/// <param name="localName">Attribute name</param>
		/// <param name="namespaceUri">Attribute namespace prefix</param>
		/// <param name="value">Attribute value</param>
		/// <returns></returns>
		internal string SetNamedStringItemInternal(XmlDocument document, XmlNode node, string localName, string namespaceUri, string value)
		{
			XmlNode namedItem = node.Attributes.GetNamedItem(localName, namespaceUri);
			if (namedItem != null)
			{
				namedItem.Value = value;
			}
			else
			{
				XmlAttribute xmlAttribute = document.CreateAttribute(localName, namespaceUri);
				xmlAttribute.Value = value;
				node.Attributes.SetNamedItem(xmlAttribute);
			}
			return value;
		}

		internal void RemoveFieldAttributeValueInternal(string attribute)
		{
			Type fieldType = typeof(SlamField);
			PropertyInfo fieldNode = fieldType.GetProperty("Node", BindingFlags.Instance | BindingFlags.NonPublic);
			RemoveNamedStringItemInternal(((XmlNode)fieldNode.GetValue(this, null)).OwnerDocument, ((XmlNode)fieldNode.GetValue(this, null)), attribute);
		}

		internal void RemoveNamedStringItemInternal(XmlDocument document, XmlNode node, string localName)
		{
			XmlNode namedItem = node.Attributes.GetNamedItem(localName);
			if (namedItem != null)
			{
				node.Attributes.RemoveNamedItem(localName);
			}
		}
	}
}

